using System.Collections;
using UnityEngine;

public class Ball : MonoBehaviour
{
	public delegate void OnCollisionDelegate(Vector3 hitPoint, HitObjectTag hitObject);

	public OnCollisionDelegate onCollision;

	public BallFlightPlan flightPlan = new BallFlightPlan();

	public PhysicMaterial tablePhysicMaterial;

	private Transform cachedTransform_;

	private Rigidbody cachedRigidbody_;

	protected GravityFlight gravityFlight = new GravityFlight();

	protected int myLayer;

	protected bool bouncePrevFrame;

	public float rotMult = 10f;

	public Transform cachedTransform
	{
		get
		{
			if (cachedTransform_ == null)
			{
				cachedTransform_ = base.transform;
			}
			return cachedTransform_;
		}
	}

	public Rigidbody cachedRigidbody
	{
		get
		{
			if (cachedRigidbody_ == null)
			{
				cachedRigidbody_ = GetComponent<Rigidbody>();
			}
			return cachedRigidbody_;
		}
	}

	public Vector3 position
	{
		get
		{
			return cachedTransform.position;
		}
		set
		{
			cachedTransform.position = value;
			prevPosition = position;
		}
	}

	public Vector3 prevPosition { get; protected set; }

	public float myRadius { get; protected set; }

	public PhysicMaterial myMaterial { get; protected set; }

	public Vector3 spin { get; protected set; }

	public bool isKinematic
	{
		get
		{
			return cachedRigidbody.isKinematic;
		}
	}

	public bool isBallInGame { get; set; }

	public void MakeKinematic()
	{
		if (!cachedRigidbody.isKinematic)
		{
			cachedRigidbody.velocity = Vector3.zero;
			cachedRigidbody.isKinematic = true;
		}
		cachedRigidbody.useGravity = false;
	}

	private void Awake()
	{
		myRadius = cachedTransform.GetComponent<Renderer>().bounds.size.y * 0.5f;
		myMaterial = cachedRigidbody.GetComponent<Collider>().sharedMaterial;
		myLayer = cachedTransform.gameObject.layer;
	}

	public void TakeBall()
	{
		MakeKinematic();
		flightPlan.Stop();
	}

	private void Update()
	{
		float deltaTime = Time.deltaTime;
		prevPosition = position;
		if (!flightPlan.isActive)
		{
			return;
		}
		BallFlightPlan.FlightPlanStage flightPlanStage = flightPlan.ActiveStage();
		cachedTransform.position = flightPlanStage.flight.Position(flightPlanStage.flight.timeOfFlight);
		flightPlan.Update(deltaTime);
		GravityFlight flight = flightPlanStage.flight;
		float magnitude = flight.initialVelocity.magnitude;
		Vector3 axis = Vector3.Cross(flight.Velocity(flight.timeOfFlight), flight.gravity.normalized);
		float angle = magnitude * Time.deltaTime * rotMult;
		cachedTransform.rotation = Quaternion.AngleAxis(angle, axis) * cachedTransform.rotation;
		if (!flightPlan.isActive)
		{
			float timeOfFlight = flight.timeOfFlight;
			cachedTransform.position = flight.Position(flight.timeOfFlight);
			Fire(flight.Velocity(flight.timeOfFlight), flight.gravity, true);
			return;
		}
		BallFlightPlan.FlightPlanStage flightPlanStage2 = flightPlan.ActiveStage();
		if (flightPlanStage2 != flightPlanStage && flightPlanStage2 != null)
		{
			ReportCollision(flightPlanStage2.flight.initialPosition, HitObjectTag.Table);
		}
	}

	private void FixedUpdate()
	{
		if (!cachedRigidbody.isKinematic)
		{
			if (isBallInGame)
			{
				Vector3 axis = Vector3.Cross(Physics.gravity.normalized, GetComponent<Rigidbody>().velocity);
				float angle = GetComponent<Rigidbody>().velocity.magnitude * Time.fixedDeltaTime * rotMult;
				cachedTransform.rotation *= Quaternion.AngleAxis(angle, axis);
			}
			if (position.y <= -0.5f)
			{
				ReportCollision(position, HitObjectTag.Floor);
				MakeKinematic();
			}
		}
	}

	private void ReportCollision(Vector3 position, HitObjectTag hitObject)
	{
		if (onCollision != null)
		{
			onCollision(position, hitObject);
		}
	}

	private void OnCollisionEnter(Collision col)
	{
		ReportCollision(position, HitObjectTag.Floor);
	}

	protected float DurationTillOutOfRange(GravityFlight flight)
	{
		float num = Table.halfwidth * 2f;
		float num2 = Table.halfLength * 1.5f;
		float c = Table.tabletopy * 0.4f;
		Vector3 initialPosition = flight.initialPosition;
		Vector3 initialVelocity = flight.initialVelocity;
		float a = 200f;
		if (initialVelocity.x != 0f)
		{
			MathEx.NullPoints onlyPositive = MathEx.FindParabolaNullPoints(flight.gravity.x * 0.5f, initialVelocity.x, Mathf.Sign(initialVelocity.x) * num - initialPosition.x).onlyPositive;
			if (onlyPositive.nullPts.Count > 0)
			{
				a = Mathf.Min(a, onlyPositive.Min);
			}
		}
		if (initialVelocity.z != 0f)
		{
			MathEx.NullPoints onlyPositive2 = MathEx.FindParabolaNullPoints(flight.gravity.z * 0.5f, initialVelocity.z, Mathf.Sign(initialVelocity.z) * num2 - initialPosition.z).onlyPositive;
			if (onlyPositive2.nullPts.Count > 0)
			{
				a = Mathf.Min(a, onlyPositive2.Min);
			}
		}
		MathEx.NullPoints onlyPositive3 = MathEx.FindParabolaNullPoints(flight.gravity.y * 0.5f, initialVelocity.y, c).onlyPositive;
		return Mathf.Min(a, onlyPositive3.Min);
	}

	protected void FireWithParams(Vector3 v, Vector3 spin, float timeToRebound, float vyRebound, Vector3 pointOfRebound)
	{
		FireWithParams(v, spin, timeToRebound, vyRebound, pointOfRebound, false, Vector3.zero);
	}

	protected void FireWithParams(Vector3 v, Vector3 spin, float timeToRebound, float vyRebound, Vector3 pointOfRebound, bool isServe, Vector3 serveRebound)
	{
		Fire(v, spin, true);
	}

	public void Fire(Vector3 velocity, Vector3 spin, bool useUnityPhysic)
	{
		if (useUnityPhysic)
		{
			StartCoroutine(DoFire(velocity));
			return;
		}
		MakeKinematic();
		gravityFlight.Fire(cachedTransform.position, velocity, Physics.gravity + spin);
	}

	private IEnumerator DoFire(Vector3 velocity)
	{
		cachedRigidbody.useGravity = false;
		cachedRigidbody.isKinematic = false;
		while (cachedRigidbody.isKinematic)
		{
			cachedRigidbody.isKinematic = false;
			yield return null;
		}
		cachedRigidbody.useGravity = true;
		cachedRigidbody.velocity = velocity;
	}

	private Vector3 Rebound(Vector3 velocity, PhysicMaterial hitMaterial)
	{
		velocity.y *= -1f * CombineBounce(hitMaterial);
		return velocity;
	}

	private float CombineBounce(PhysicMaterial hitMaterial)
	{
		if (hitMaterial == null)
		{
			return myMaterial.bounciness;
		}
		switch (hitMaterial.bounceCombine)
		{
		case PhysicMaterialCombine.Average:
			return (myMaterial.bounciness + hitMaterial.bounciness) * 0.5f;
		case PhysicMaterialCombine.Maximum:
			return Mathf.Max(myMaterial.bounciness, hitMaterial.bounciness);
		case PhysicMaterialCombine.Minimum:
			return Mathf.Min(myMaterial.bounciness, hitMaterial.bounciness);
		case PhysicMaterialCombine.Multiply:
			return myMaterial.bounciness * hitMaterial.bounciness;
		default:
			return myMaterial.bounciness;
		}
	}

	public void HitBall(Vector3 landingPos, float hn, float timeToLand, float spinX, float spinY, PlayerBase playerThatHit, bool useNormalTime = true, float airResistance = 0f, float rebound = 1f, float racketTimeScale = 1f)
	{
		prevPosition = position;
		Vector3 vector = (landingPos - position).OnGround();
		Vector3 vector2 = new Vector3(vector.z, 0f, (0f - vector.x) * spinX);
		vector2.Normalize();
		Vector3 theSpin = new Vector3(Mathf.Sign(vector2.x) * spinX, 0f - spinY, 0f);
		float magnitude = vector.magnitude;
		Vector3 normalized = vector.OnGround().normalized;
		Vector3 vector3 = (0f - airResistance) * 2f * magnitude / (timeToLand * timeToLand) * normalized;
		theSpin += vector3;
		ParabolicFlight parabolicFlight = new ParabolicFlight(cachedTransform.position, landingPos, hn, theSpin, Table.tabletopy);
		float num = parabolicFlight.c / parabolicFlight.g;
		float num2 = Mathf.Sqrt(num);
		if (num > 0f && num2 < 4f && useNormalTime)
		{
			timeToLand = num2;
			Debug.Log("NORMAL TIME " + num2 + " SPEED " + Vector3Ex.HorizontalDistance(position, landingPos) / num2);
		}
		else if (timeToLand < num2)
		{
			parabolicFlight.SetSpinYToMatchTime(timeToLand);
			Debug.Log("Spin to match time " + parabolicFlight.spin);
		}
		else if (timeToLand > num2)
		{
			float b = parabolicFlight.hnToMatchTime(timeToLand);
			parabolicFlight.hn = Mathf.Min(parabolicFlight.hn + 0.2f, b);
			parabolicFlight.SetSpinYToMatchTime(timeToLand);
			Debug.Log("height to match time " + parabolicFlight.hn);
		}
		Debug.Log("SPEED " + Vector3Ex.HorizontalDistance(position, landingPos) / timeToLand);
		float time = timeToLand;
		Vector3 velocity = parabolicFlight.initialVelocityWithTime(time);
		Vector3 vector4 = parabolicFlight.spin;
		Debug.Log("SPIN " + vector4);
		flightPlan.Reset();
		flightPlan.racketTimeScale = racketTimeScale;
		flightPlan.playerThatHitBall = playerThatHit;
		flightPlan.isServeBall = false;
		BallFlightPlan.FlightPlanStage flightPlanStage = flightPlan.AddStage();
		flightPlanStage.flight.Fire(cachedTransform.position, velocity, Physics.gravity + vector4);
		flightPlanStage.duration = timeToLand;
		flightPlan.timeToLand = timeToLand;
		flightPlan.landingPos = landingPos;
		if (Table.IsPosOnTable(landingPos))
		{
			Vector3 velocity2 = Rebound(flightPlanStage.flight.Velocity(timeToLand), tablePhysicMaterial);
			Vector3 vector5 = flightPlanStage.flight.Position(timeToLand);
			velocity2.x *= rebound;
			velocity2.z *= rebound;
			velocity2.y = Mathf.Sqrt(-2f * Physics.gravity.y * 0.2f);
			BallFlightPlan.FlightPlanStage flightPlanStage2 = flightPlan.AddStage();
			flightPlanStage2.flight.Fire(vector5, velocity2, Physics.gravity);
			flightPlanStage2.duration = Mathf.Min(DurationTillOutOfRange(flightPlanStage2.flight), 1.8f * flightPlanStage2.flight.apexTime);
			flightPlan.reboundStage = flightPlanStage2;
		}
		else
		{
			flightPlanStage.duration = DurationTillOutOfRange(flightPlanStage.flight);
		}
		MakeKinematic();
		flightPlan.Start();
	}

	public void HitServe(Vector3 landingPos, float hn, float timeToLand, float spinX, PlayerBase playerThatHit, float airResistance, float rebound, float racketTimeScale)
	{
		Vector3 vector = cachedTransform.position;
		float num = vector.y - Table.tabletopy;
		float num2 = timeToLand;
		float num3 = 1f / myMaterial.bounciness;
		float num4 = Mathf.Max(hn, 1.1f * num / (num3 * num3));
		float num5 = num2 * num2;
		float num6 = num5 * num5;
		float num7 = (2f + num3) * (2f + num3);
		float num8 = 4f * num5 * num7;
		float num9 = num3 * num3;
		float num10 = -2f * num4 * (num7 - num9) - 2f * num;
		float a = num6;
		float b = 2f * (num5 * num10 + num8 * num4);
		float c = num10 * num10;
		float num11 = Physics.gravity.y;
		Vector3 zero = Vector3.zero;
		Vector3 vector2 = (landingPos - position).OnGround();
		Vector3 vector3 = new Vector3(vector2.z, 0f, (0f - vector2.x) * spinX);
		vector3.Normalize();
		Vector3 vector4 = new Vector3(Mathf.Sign(vector3.x) * spinX, 0f, 0f);
		float magnitude = vector2.magnitude;
		Vector3 normalized = vector2.OnGround().normalized;
		Vector3 vector5 = (0f - airResistance) * 2f * magnitude / (timeToLand * timeToLand) * normalized;
		vector4 += vector5;
		zero = vector4;
		float num12 = Mathf.Sqrt(-2f * num11 * num4);
		float num13 = -2f * num12 / num11;
		float min = MathEx.FindParabolaNullPoints((0f - num11) / 2f, (0f - num3) * num12, num).onlyPositive.Min;
		if (num13 + min > timeToLand)
		{
			MathEx.NullPoints nullPoints = MathEx.FindParabolaNullPoints(a, b, c);
			foreach (float nullPt in nullPoints.nullPts)
			{
				if (!(nullPt > 0f))
				{
					zero.y = nullPt - Physics.gravity.y;
					num11 = nullPt;
					if (nullPt < Physics.gravity.y)
					{
						break;
					}
				}
			}
		}
		else
		{
			float num14 = -2f * (num7 - num9);
			float num15 = num5 * num11 - 2f * num;
			MathEx.NullPoints onlyPositive = MathEx.FindParabolaNullPoints(num14 * num14, 2f * (num15 * num14 + num8 * num11), num15 * num15).onlyPositive;
			foreach (float nullPt2 in onlyPositive.nullPts)
			{
				num4 = Mathf.Max(num4, nullPt2);
			}
			num4 = Mathf.Max(num4, 1.1f * num / (num3 * num3));
		}
		num12 = Mathf.Sqrt(-2f * num11 * num4);
		num13 = -2f * num12 / num11;
		min = MathEx.FindParabolaNullPoints((0f - num11) / 2f, (0f - num3) * num12, num).onlyPositive.Min;
		timeToLand = min + num13;
		float y = (0f - num11) * min - num3 * num12;
		float num16 = landingPos.x - vector.x;
		float num17 = landingPos.z - vector.z;
		float x = num16 / timeToLand - zero.x * timeToLand / 2f;
		float z = num17 / timeToLand - zero.z * timeToLand / 2f;
		Vector3 vector6 = new Vector3(x, y, z);
		Vector3 velocity = vector6;
		velocity.x += zero.x * min;
		velocity.z += zero.z * min;
		velocity.y = num12;
		flightPlan.Reset();
		flightPlan.racketTimeScale = racketTimeScale;
		flightPlan.landingPos = landingPos;
		flightPlan.playerThatHitBall = playerThatHit;
		flightPlan.isServeBall = true;
		MakeKinematic();
		BallFlightPlan.FlightPlanStage flightPlanStage = flightPlan.AddStage();
		flightPlanStage.flight.Fire(position, vector6, Physics.gravity + zero);
		flightPlanStage.duration = min;
		Vector3 vector7 = flightPlanStage.flight.Position(flightPlanStage.duration);
		flightPlan.timeToLand = timeToLand;
		BallFlightPlan.FlightPlanStage flightPlanStage2 = flightPlan.AddStage();
		flightPlanStage2.flight.Fire(vector7, velocity, Physics.gravity + zero);
		flightPlanStage2.duration = num13;
		if (Table.IsPosOnTable(landingPos))
		{
			Vector3 velocity2 = Rebound(flightPlanStage2.flight.Velocity(num13), tablePhysicMaterial);
			Vector3 vector8 = flightPlanStage2.flight.Position(num13);
			BallFlightPlan.FlightPlanStage flightPlanStage3 = flightPlan.AddStage();
			velocity2.x *= rebound;
			velocity2.z *= rebound;
			velocity2.y = Mathf.Sqrt(-2f * Physics.gravity.y * 0.2f);
			flightPlanStage3.flight.Fire(vector8, velocity2, Physics.gravity);
			flightPlanStage3.duration = DurationTillOutOfRange(flightPlanStage3.flight);
			flightPlan.reboundStage = flightPlanStage3;
		}
		else
		{
			flightPlanStage2.duration = DurationTillOutOfRange(flightPlanStage2.flight);
		}
		flightPlan.Start();
	}

	public Vector3 spinAfterRebound(Vector3 spin)
	{
		spin *= 0.7f;
		spin.x = 0f;
		spin.z = 0f;
		return spin;
	}
}
